package com.um.jdy.tcp.netty;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.um.jdy.tcp.properties.HshNettyProperties;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.nio.charset.Charset;

/**
 * @author 邓南辉
 * @date 2020/2/13
 * @copyright 广州优匠科技有限公司
 */
@Slf4j
@Component
@ChannelHandler.Sharable
public class NettyChannelHandler extends SimpleChannelInboundHandler<Object> {

    @Autowired
    SDKCommandHandler commandHandler;

    @Autowired
    HshNettyProperties hshNettyProperties;

    /**
     * 绑定
     *
     * @param ctx
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        log.debug("新的连接：{} {}", ctx.channel().remoteAddress(), ctx.channel().id());
        ctx.fireChannelActive();
    }

    /**
     * 取消绑定
     *
     * @param ctx
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        String deviceName = NettyChannelAttrs.getDeviceName(ctx);
        commandHandler.deviceOffline(deviceName, ctx);

        Integer idleNum = NettyContextHolder.removeContext(ctx);
        ctx.channel().close();
        //没收到过数据的连接用debug日志，否则用info日志，避免负载均衡产生过多日志
        if (idleNum == null) {
            log.debug("连接断开[{}]：{} {}", deviceName, ctx.channel().remoteAddress(), ctx.channel().id());
        } else {
            log.info("连接断开[{}]：{} {}", deviceName, ctx.channel().remoteAddress(), ctx.channel().id());
        }
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
            if (idleStateEvent.state() == IdleState.READER_IDLE) {
                //向客户端发送消息
                //ctx.writeAndFlush("pong").addListener(ChannelFutureListener.CLOSE_ON_FAILURE);

                String deviceName = NettyChannelAttrs.getDeviceName(ctx);
                Integer idleNum = NettyContextHolder.increaseIdleNum(ctx.channel().id().asLongText());
                if (idleNum >= hshNettyProperties.getIdleCloseCount()) {//大于允许空闲次数，服务器则主动断开
                    commandHandler.deviceOffline(deviceName, ctx);

                    NettyContextHolder.removeContext(ctx);
                    ctx.channel().close();
                    log.info("[{}]{}已经{}次没有收到客户端的心跳，服务器主动断开", deviceName, ctx.channel().id(), idleNum);
                } else {
                    log.debug("[{}]{}已经是第{}次没有发送心跳了", deviceName, ctx.channel().id(), idleNum);
                }
            }
        }
        ctx.fireUserEventTriggered(evt);
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, Object msg) {
        if (msg == null) {
            return;
        }

        //收到客户端信息，需要重置未发送心跳的次数为0
        Integer idleNum = NettyContextHolder.resetIdleNum(ctx.channel().id().asLongText());
        //新连接首次收到数据
        if (idleNum == null) {
            log.info("新的数据连接：{} {}", ctx.channel().remoteAddress(), ctx.channel().id());
        }

        ByteBuf byteBuf = (ByteBuf) msg;
        String json = byteBuf.toString(Charset.defaultCharset());
        log.debug("收到命令：{}-{}", ctx.channel().id(), json);

        if (JSONUtil.isTypeJSON(json)) {
            commandHandler.handleCommand(json, ctx);
        } else {
            log.error("接收到命令不是json格式：{}", json);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        if (cause instanceof IOException && "Connection reset by peer".equals(cause.getMessage())) {
            log.debug(StrUtil.format("客户端异常：{} {}", ctx.channel().remoteAddress(), ctx.channel().id()), cause);
        } else {
            log.error(StrUtil.format("客户端异常：{} {}", ctx.channel().remoteAddress(), ctx.channel().id()), cause);
        }
        ctx.channel().close();
    }
}
